package server;



import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Timer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.log4j.BasicConfigurator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PasswordFinder;

import service.AuctionEvent;
import service.AuctionHolder;
import service.BidEvent;
import service.MyTimerTask;
import service.SecurityService;

import domain.Auction;

import server.BillAuctionInterface;


public class AuctionServer {


	private static final int maxClients = 100000;
	private static ExecutorService threadPool;
	private int port;
	private Timer timer;
	private ArrayList<AuctionServerThread> threads = new ArrayList<AuctionServerThread>();
	private ProcessEventInterface analyticsServer;
	private BillingServerInterface billingServer;
	private BillAuctionInterface billingServerSecure;
	private String analyticsName;
	private String billingName;
	private int rmiPort;
	private String host;
	private ServerSocket server = null;
	private boolean listening = true;
	private AuctionServerUserInputThread inputthread;
	private Registry registry;
	private boolean billingAvailable = true;
	private boolean analyticsAvailable = true;
	private ArrayList<AuctionServerThread> all = new ArrayList<AuctionServerThread>();
	

	private String serverKeyPath;
	private String clientKeysPath;
	private PrivateKey serverKey;


	private void run() {
		initPrivateKey();

		inputthread = new AuctionServerUserInputThread(this);
		inputthread.start();


		try {
			server = new ServerSocket(port);
		} catch (IOException e) {
			System.out.println("Error binding socket to port "+port);
			listening = false;
			e.printStackTrace();
		}


		try {
			Properties props = new Properties();
			java.io.InputStream is = ClassLoader.getSystemResourceAsStream("./registry.properties");
			props.load(is);

			if (is != null) {
				try {			


					rmiPort = Integer.parseInt(props.getProperty("registry.port"));
					host = props.getProperty("registry.host");

				} catch (NumberFormatException e) {
					e.printStackTrace();
				} finally {
					try {
						is.close();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			} else {
				System.err.println("Properties file not found!");
			}
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}



		try {
			registry = LocateRegistry.getRegistry(host,rmiPort); //TODO argument handling

		} catch (RemoteException e) {
			System.out.println("Warning: Registry unavailable!");
		} 

		try {
			billingServer = (BillingServerInterface) registry.lookup(billingName);
			billingServerSecure = (BillAuctionInterface) billingServer.login("auctionServer", "server");
		} catch (RemoteException e) {
			if(billingAvailable) {
				billingAvailable=false;
				System.out.println("Warning: BillingServer unavailable!");
			}

		} catch (NotBoundException e) {
			if(billingAvailable) {
				billingAvailable=false;
				System.out.println("Warning: BillingServer unavailable!");
			}
		}

		try {
			analyticsServer = (ProcessEventInterface) registry.lookup(analyticsName);

		} catch (RemoteException e) {
			if(analyticsAvailable) {
				analyticsAvailable = false;
				System.out.println("Warning: AnalyticsServer unavailable!");
			}

		} catch (NotBoundException e) {
			if(analyticsAvailable) {
				analyticsAvailable = false;
				System.out.println("Warning: AnalyticsServer unavailable!");
			}
		}		

		//System.out.println("Server started.") ;
		while(listening) {
			try {
				AuctionServerThread newThread = new AuctionServerThread(server.accept(),this,analyticsServer,serverKey,clientKeysPath);
				threads.add(newThread);
				all.add(newThread);
				threadPool.execute(newThread);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		for(int i=0;i<threads.size();i++) {
			threads.get(i).shutDown();
		}
		threadPool.shutdown();
		//System.out.println("Server Stopped.") ;

	}


	private void initPrivateKey() {
		PEMReader in = null;
		KeyPair keyPair = null;
		boolean wrongPassword = true;
		while(wrongPassword == true) {
			try {
				in = new PEMReader(new FileReader(serverKeyPath), new PasswordFinder() {

					@Override
					public char[] getPassword() {
						// reads the password from standard input for decrypting the private key
						System.out.println("Enter pass phrase:");
						try {
							return new BufferedReader(new InputStreamReader(System.in)).readLine().toCharArray();
						} catch (IOException e) {
							e.printStackTrace();
							return null;
						}
					} 

				});

			} catch (FileNotFoundException e) {
				System.out.println("Key File not found. Aborting.");
				shutServerDown();
				break;
			}


			Security.addProvider(new BouncyCastleProvider()); 

			try {
				keyPair = (KeyPair) in.readObject();
				in.close();
				wrongPassword = false;
			} catch (IOException e) {
				System.out.println("Wrong password. Try again...");
			}
		}
		if(wrongPassword == false) this.serverKey = keyPair.getPrivate(); 

	}


	private AuctionServer(String args[]) {
		checkInput(args);
		run();
	}

	public static void main(String args[]) {
		threadPool = Executors.newCachedThreadPool();
		//BasicConfigurator.configure();
		AuctionServer auctionServer = new AuctionServer(args);
	}

	public void broadcastMsg(String str){
		for (int i = 0; i < all.size(); i++) {
			AuctionServerThread ch = all.get(i);
			
			if(ch.checkRightUser(str) == true){
				ch.printOut(str);
			}			
		}
	}
	
	public void broadcastRejected(String str){
		for (int i = 0; i < all.size(); i++) {
			AuctionServerThread ch = all.get(i);
		
			if(ch.checkRightUserRejected(str) == true){
				ch.printOut("!rejected ");
			}
			if(ch.checkRightOwnerRejected(str) == true){
				ch.printOut(str);
			}
			
		}
	}
	
	public void broadcastUserConfirmed(String str2){
		for (int i = 0; i < all.size(); i++) {
			AuctionServerThread ch = all.get(i);
			
			if(ch.checkGroupBidUser(str2) == true){
				ch.printOut("!rejected GroupBid has ended.");
			}
			
		}
	}
	
	public void sendExitGroupBid(String owner) {
		for (int i = 0; i < all.size(); i++) {
			AuctionServerThread ch = all.get(i);
			
			if(ch.checkGroupBidOwner(owner) == true){
				ch.printOut("GroupBid was quit");
			}
			
		}	
	}

	private void usage() {
		System.out.println("Usage: java AuctionServer <tcpport> <analytics server binding name> <billing server binding name>\nExample: java AuctionServer 12345 analyticsServer billingServer");
		threadPool.shutdown();
		System.exit(0);
	}


	private void checkInput(String[] args) {
		int tempport = -1;
		if(args.length!=5) {
			usage();
		}
		try{
			tempport = Integer.parseInt(args[0]);
		} catch(NumberFormatException e) {
			usage();
		}
		if(tempport>-1) {
			port = tempport;
		} else usage();
		analyticsName = args[1];
		billingName = args[2];
		serverKeyPath = args[3];
		clientKeysPath = args[4];


	}

	public void createTimer(Auction auction) {

		//System.out.println("creating the timer");
		timer = new Timer(true);
		MyTimerTask timerTask = new MyTimerTask(auction, this);
		timer.schedule(timerTask,auction.getDate());

	}
	

	public void notifyAuctionEnded(Auction auction) {
		/*for(int i=0;i<threads.size();i++) {
			//System.out.println("informing thread number "+i);
			threads.get(i).notifyAuctionEnded(auction);
		}*/
		try {
			if(analyticsServer != null) analyticsServer.processEvent(new AuctionEvent("AUCTION_ENDED",auction.getId()));
			if(auction.getBid()>0) {
				if(analyticsServer != null) analyticsServer.processEvent(new BidEvent("BID_WON",auction.getBidder(),auction.getId(),auction.getBid()));
			}

		} catch (RemoteException e) {
			if(analyticsAvailable) {
				analyticsAvailable = false;
				System.out.println("Warning: AnalyticsServer unavailable!");
			}
		}

		try {
			if(billingServerSecure != null) billingServerSecure.billAuction(auction.getSeller(), auction.getId(), auction.getBid());
		} catch (RemoteException e) {
			if(billingAvailable) {
				billingAvailable = false;
				System.out.println("Warning: BillingServer unavailable!");
			}
		}
		//System.out.println("removing auction");
		AuctionHolder.getInstance().removeAuction(auction);
	}	


	public void overbid(Auction auction, String oldBidder) {
		/*for(int i=0;i<threads.size();i++) {
			//System.out.println("informing thread number "+i);
			threads.get(i).overbid(auction, oldBidder);
		}*/
		//System.out.println("overbid auction");

	}


	public void closeThread(AuctionServerThread auctionServerThread) {
		threads.remove(auctionServerThread);
	}

	public void warningBilling() {
		if(billingAvailable) {
			billingAvailable = false;
			System.out.println("Warning: BillingServer unavailable!");
		}
	}

	public void warningAnalytics() {
		if(analyticsAvailable) {
			analyticsAvailable = false;
			System.out.println("Warning: AnalyticsServer unavailable!");
		}
	}

	public void shutServerDown() {
		this.listening = false;
	}

}
